package com.ihy.service.impl;

import com.ihy.dao.hibernate.DaoUser;
import com.ihy.domain.User;
import com.ihy.service.ServiceUser;
import com.ihy.utils.RedisTemplateUtil;
import com.ihy.vo.ResultResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

import static com.ihy.utils.SendPhoneUtil.getPhoneMsg;
import static com.ihy.utils.SendPhoneUtil.getRandom;

/**
 * 用户信息表业务层
 * <p>
 * Transactional注解：
 * 描述单个方法或类的事务属性。
 * 在类级别，此注解作为默认值应用于声明类及其子类的所有方法。注意，它不适用于类层次结构上的祖先类；方法需要在本地重新声明，以便参与子类级别的注解。
 * <p>
 * Service注解：
 * 表示带注解的类是“服务”，最初设计定义为“作为独立于模型中的接口提供的操作，没有封装状态。”
 * 还可能表示类是“业务服务外观”（在核心J2EE模式意义上）或类似的东西。
 * 此注解充当@Component的专门化，允许通过类路径扫描自动检测实现类。
 * <p>
 * Autowired注解:
 * 将构造函数、字段、setter方法或config方法标记为由Spring的依赖注入工具自动连接。
 * Autowired注解是按照类型（byType）装配依赖对象，默认情况下它要求依赖对象必须存在，如果允许null值，可以设置它的required属性为false。
 * 如果我们想使用按照名称（byName）来装配，可以结合@Qualifier注解一起使用。(通过类型匹配找到多个candidate,在没有@Qualifier、@Primary注解的情况下，会使用对象名作为最后的fallback匹配)
 * <p>
 * Override注解：
 * 指示方法声明旨在重写父类型中的方法声明。如果使用此注解类型对方法进行注解，则需要编译器生成错误消息，除非至少满足以下条件之一：
 * 该方法重写或实现在父类型中声明的方法。
 * 该方法具有一个重写的签名，该签名等同于在对象中声明的任何公共方法的签名。
 * <p>
 * CachePut注解:
 * 指示方法（或类上的所有方法）触发缓存放置操作。
 * 与@Cacheable注解不同，此注解不会导致跳过建议的方法。相反，它总是导致调用方法，并将其结果存储在关联的缓存中。
 * 注意，Java8的可选返回类型将自动处理，如果存在，则其内容存储在缓存中。
 * 此注解可用作元注解，以创建具有属性替代的自定义组合注解。
 * <p>
 * CacheEvict注解：
 * 表示方法（或类上的所有方法）触发缓存逐出操作的注解。
 * 此注解可用作元注解，以创建具有属性覆盖的自定义组合注解。
 * <p>
 * CacheAble注解：
 * 表示调用方法（或类中所有方法）的结果可以缓存的注解。
 * 每次调用建议的方法时，都会应用缓存行为，检查是否已经为给定参数调用了该方法。合理的默认值只是使用方法参数来计算键，但是可以通过key属性或自定义org.springframework.cache.interceptor.KeyGenerator实现可以替换默认的实现（请参见keyGenerator）。
 * 如果在缓存中找不到计算键的值，则将调用目标方法，并将返回的值存储在关联的缓存中。请注意，Java8的可选返回类型是自动处理的，其内容存储在缓存中（如果存在）。
 * 此注解可用作元注解，以创建具有属性覆盖的自定义组合注解。
 */
@Service
@Transactional
@Slf4j
public class ServiceUserImpl implements ServiceUser {
    @Autowired
    private DaoUser repository;
    //加密方式
    @Autowired
    private PasswordEncoder passwordEncoder;
    @Autowired
    private ResultResponse resultResponse;
    @Autowired
    private RedisTemplateUtil redisTemplateUtil;

    /**
     * @param user 用户对象
     * @return 调用查询一个用户的方法
     */
    @Override
    @CachePut(key = "#p0.username", value = "redisCacheTime")
    public User insert(User user) {
        //加密密码
        user.setPassword(passwordEncoder.encode(user.getPassword()));
        repository.save(user);
        return query(user);
    }

    /**
     * @param user 用户对象
     */
    @CacheEvict(key = "#p0.username", value = "redisCacheTime")
    @Override
    public void delete(User user) {
        repository.delete(user);
    }

    /**
     * @param user 用户对象
     * @return 调用查询一个用户的方法
     */
    @Override
    @CachePut(key = "#p0.username", value = "redisCacheTime")
    public User update(User user) {
        //加密密码
        user.setPassword(passwordEncoder.encode(user.getPassword()));
        repository.update(user.getId(), user.getUsername(), user.getPassword());
        return query(user);
    }

    /**
     * 事务处理 查询操作为只读 如果其他bean调用这个方法,在其他bean中声明事务,那就用事务.如果其他bean没有声明事务,那就不用事务
     *
     * @return 所有用户的信息
     */
    @Override
    @Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
    @Cacheable(value = "redisCacheTime")
    public List<User> query() {
        return repository.findAll();
    }

    /**
     * 加入缓存以解决每次查询都会查数据库,降低效率的问题
     *
     * @param user 用户信息
     * @return 一个用户的信息
     */
    @Override
    //缓存结果不为空的时候存入redis 并且缓存键为用户名 值为存入redis的位置
    @Cacheable(key = "#p0.username", value = "redisCacheTime", unless = "#result==null")
    @Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
    public User query(User user) {
        return repository.getByUsername(user.getUsername());
    }

    /**
     * 根据手机号获取用户信息
     *
     * @param phone 电话号码
     * @return 用户信息
     */
    @Override
    @Cacheable(key = "'userPhone'+#p0", value = "redisCacheTime", unless = "#result==null")
    @Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
    public User getByPhone(String phone) {
        return repository.getByPhone(phone);
    }

    /**
     * 获取验证码
     *
     * @param phone     手机号
     * @param phoneData 用户信息
     * @return 返回前端信息
     */
    @Override
    public ResultResponse getMessage(String phone, User phoneData) {
        //生成随机数
        String code = null;
        //发送状态
        String phoneMsg;
        //遍历数据拿出用户信息
        if (phoneData != null) {
            //从redis数据库中取出验证码信息 如没有跳过这个判断
            code = (String) redisTemplateUtil.get(phoneData.getUsername() + "PhoneCode");
        }
        if (code == null) {
            //新建一个验证码信息存入redis用于后续验证
            code = getRandom(6);
            //调用方法 发送验证码
            phoneMsg = getPhoneMsg(phone, code);
            //存入redis当前发送的验证码
            if (phoneData != null) redisTemplateUtil.set(phoneData.getUsername() + "PhoneCode", code, 5000);
            else redisTemplateUtil.set(phone + "PhoneCode", code, 5000);
            //打印发送状态
            log.info("发送状态：{} 手机号：{} 验证码：{}", phoneMsg, phone, code);
            //返回前端数据
        } else {
            //调用方法 发送验证码
            phoneMsg = getPhoneMsg(phone, code);
            //打印发送状态
            log.info("重发状态：{} 手机号：{} 验证码：{}", phoneMsg, phone, code);
            //返回前端数据
        }
        if (phoneData != null) resultResponse = isMessage(phoneData.getId(), phoneMsg, null, null, true);
        else resultResponse = isMessage(-1, phoneMsg, null, null, true);
        return resultResponse;
    }

    /**
     * 验证 验证码
     *
     * @param phoneCode    用户输入的验证码
     * @param code         系统生成的验证码
     * @param phoneData    用户信息
     * @param password     用户输入的新密码
     * @param userPassword 用户输入的旧密码
     * @return 返回前端数据
     */
    @Override
    public ResultResponse isMessage(String phoneCode, String code, User phoneData, String password, String userPassword) {
        //判断当用户输入的验证码与发送的验证码匹配的时候通过验证并修改密码，否则重复else里面的语句
        resultResponse = isMessage(phoneData.getId(), null, phoneCode, code, false);
        if (resultResponse.getMessage().equals("验证码正确")) {
            phoneData.setId(phoneData.getId());
            phoneData.setPassword(password);
            //修改操作
            User updateUser = update(phoneData);
            if (!userPassword.equals(updateUser.getPassword())) {
                resultResponse.setMessage("密码已修改");
                redisTemplateUtil.del(phoneData.getUsername() + "PhoneCode");
                redisTemplateUtil.del("redisCacheTime::" + phoneData.getUsername());
                redisTemplateUtil.del("redisCacheTime::userPhone" + phoneData.getPhone());
            }
        }
        return resultResponse;
    }

    /**
     * @param id        用户id
     * @param phoneMsg  验证信息
     * @param phoneCode 用户输入的验证码
     * @param code      系统生成的验证码
     * @param state     判断分支条件
     * @return 返回前端数据
     */
    public ResultResponse isMessage(Integer id, String phoneMsg, String phoneCode, String code, Boolean state) {
        if (state) {
            if (phoneMsg == null) {
                resultResponse.setCode(401);
                resultResponse.setMessage("发送失败");
                resultResponse.setData(null);
            } else if (!phoneMsg.equals("发送成功")) {
                resultResponse.setCode(401);
                resultResponse.setMessage(phoneMsg);
                resultResponse.setData(null);
            } else {
                resultResponse.setCode(200);
                resultResponse.setMessage("发送成功");
                resultResponse.setData(id);
            }
        } else {
            if (phoneCode.equals(code)) {
                resultResponse.setCode(200);
                resultResponse.setMessage("验证码正确");
                resultResponse.setData(id);
            } else {
                resultResponse.setCode(401);
                resultResponse.setMessage("验证码错误");
                resultResponse.setData(null);
            }
        }
        return resultResponse;
    }
}